---
title: "Part 4: Utilities"
---

import { LinkButton } from "gatsby-interface"
import { MdArrowForward } from "react-icons/md"

## Introduction

In the previous parts you built the foundation of your source plugin, from now on it's about adding new functionalities, improving the user experience, and making the plugin better overall.

This part of the tutorial will focus on a subset of the [Node API helpers](/docs/reference/config-files/node-api-helpers/). Feel free to revisit this document as it can also work as standalone instructions for those APIs.

By the end of this part of the tutorial, you will be able to:

- Use the `reporter` API to output structured terminal messages
- Use the `cache` API to save artifacts between runs
- Define custom errors for your plugin through `reporter.setErrorMap`
- Use the `pluginOptionsSchema` API to verify your plugin's options

## `reporter` API

You, as a source plugin author, can interact with your users in a variety of ways. You can share information in the README, chat in issues & PRs, create guides, etc. But no matter how active of a maintainer you are, it is your source plugin that will interact the most with your users through the terminal when they run `gatsby develop` or `gatsby build`. Gatsby outputs what it does behind the scenes with the goal of giving the users rich information that helps them be more productive with Gatsby itself.

For this, Gatsby internally uses the `reporter` API and you can use it, too! When adding logs to the terminal be mindful of keeping of them concise, informative, and to a minimum. Most Gatsby plugins don't even need to add logs (other than errors), so in doubt leave them out at the beginning and reevaluate once you have a bigger user base.

If you need information about an API, always be sure to check Gatsby's API docs, in this case the [reporter API docs](/docs/reference/config-files/node-api-helpers/#reporter). The `reporter` API is available in all Node APIs and its methods can be used like this:

```ts
// Doesn't have to only be onPluginInit, any Node API has it
export const onPluginInit = ({ reporter }) => {
  reporter.info(`Hello World!`)
}
```

Here's a short explanation on the most important methods:

- `info`: Print a message to the terminal.
- `warn`: Print a warning message to the terminal.
- `error`: Print an error message to the terminal.
- `panic`: Print an error message to the terminal and immediately exit the process.
- `panicOnBuild`: Print an error message to the terminal and immediately exit the process (only during `gatsby build`). Most often you should use this over `panic` as it'll allow users to debug the error during `gatsby develop`.
- `verbose`: Print a message to the terminal that is only visible when the "verbose" flag is enabled (e.g. `gatsby build --verbose`).
- `activityTimer`: Print an informational message to the terminal that has a timer attached to it (e.g. how long that step took).
- `setErrorMap`: Set a custom error map to the reporter. This allows the reporter to extend Gatsby's internal error map.

You'll learn how to use `panicOnBuild`, `activityTimer`, and `setErrorMap` in the next three tasks as they are the most relevant and common for plugins.

### Task: Output errors

As source plugins interact with third-party APIs they are inherently prone to errors (network issues, API outages, etc.). As explained in [Part 2](/docs/tutorial/creating-a-source-plugin/part-2/#fetch-data) it's good practice to gracefully handle all possible error states, as there's nothing worse than no actionable feedback at all.

In Part 2 you also added the `fetchGraphQL` function that accesses the GraphQL API inside the `api` folder. When you send a malformed request to it, it can not only give back `data` but also `errors`. Use that information to output it with `panicOnBuild`.

1. Go to your `plugin/src/source-nodes.ts` file. Destructure `reporter` from `gatsbyApi` at the top of `sourceNodes`. Add `errors` to the destructuring statement for the `fetchGraphQL` response. Also add a conditional statement when `errors` is truthy directly below `fetchGraphQL`:

    ```ts:title=plugin/src/source-nodes.ts
    // Imports

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      // highlight-next-line
      const { reporter } = gatsbyApi

      // Types

      // highlight-next-line
      const { data, errors } = await fetchGraphQL<IApiResponse>(
        `http://localhost:4000/graphql`,
        `query FetchApi {
          posts {
            id
            slug
            title
            image {
              url
              alt
              width
              height
            }
            author
          }
          authors {
            id
            name
          }
        }
        `
      )

      // highlight-start
      if (errors) {
        // Do something...
      }
      // highlight-end

      // Rest of sourceNodes
    }

    // Rest of file
    ```

1. Inside the `errors` conditional, use `panicOnBuild` to output the error message that you're receiving from your API. `error`, `panicOnBuild`, and `panic` accept a `string`, `Error` or a structured error (you'll learn about the latter in [Task: Define custom errors](#task-define-custom-errors)). Also add an early return to not continue creating nodes.

    ```ts:title=plugin/src/source-nodes.ts
    // Imports

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      // Rest of sourceNodes

      if (errors) {
        // highlight-next-line
        reporter.panicOnBuild(errors[0].message)

        // highlight-next-line
        return
      }

      // Rest of sourceNodes
    }

    // Rest of file
    ```

    <Announcement>

    **Pro tip:** Try to rely on TypeScript types to check if the error you're getting back is a valid `Error` type. If not, you might need to modify the error a little bit to fit the requirements.

    </Announcement>

1. To see if it works, add a typo to your GraphQL request inside `fetchGraphQL`:

    ```ts:title=plugin/src/source-nodes.ts
    const { data, errors } = await fetchGraphQL<IApiResponse>(
      `http://localhost:4000/graphql`,
      `query FetchApi {
        posts {
          id
          slug
    // highlight-next-line
          title2
          image {
            url
            alt
            width
            height
          }
          author
        }
        authors {
          id
          name
        }
      }
      `
    )
    ```

    Restart the `develop:site` script and you should see an error in the terminal:

    ```shell
    ERROR  UNKNOWN

    Cannot query field "title2" on type "Post". Did you mean "title"?
    ```

    Yes, it works 🎉 Don't forget to remove the typo again. In [Task: Define custom errors](#task-define-custom-errors) you'll learn how to further customize this error.

    <Announcement>

    **Pro tip:** This is the error that the GraphQL API directly returns. You can see the same error if you go to `http://localhost:4000/graphql` (the GraphQL API from `api` folder) and try the query there. Get yourself familiar with how, when, and in which form your API throws errors.

    </Announcement>

You're now surfacing any errors that occur during the data fetching to your users. Use the `error`, `panicOnBuild`, and `panic` methods throughout your code to display all relevant errors to your user.

**Please note:** If necessary, add context to your error messages instead of only passing the error along. This is especially true if the error could be thrown in multiple places or if the original error is not clear enough.

### Task: Show activities and timings

Since your source plugin is retrieving information from a third-party API, that process could take a while, especially if it's fetching a lot of data. In these instances you can use the `activityTimer` method to let the user know that something is happening in the background. You shouldn't add separate `activityTimer` instances (remember: Keep the output concise and to a minimum) but instead use the `setStatus` method on `activityTimer` to update its text.

Now you'll learn exactly that, by adding an activity for the sourcing and node creation process of your plugin.

1. Create a new `activityTimer` and store that in a variable (the name doesn't matter). You'll use the variable to start, update, and stop the timer. Try to use a text that clearly indicates your plugin name and what it's doing.

    ```ts:title=plugin/src/source-nodes.ts
    // Imports

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      const { reporter } = gatsbyApi

      const sourcingTimer = reporter.activityTimer(`Sourcing from plugin API`)

      // Rest of sourceNodes
    }

    // Rest of file
    ```

1. Since the `sourcingTimer` was initialized before the `fetchGraphQL` call you can also `start()` it before that call:

    ```ts:title=plugin/src/source-nodes.ts
    // Imports

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      const { reporter } = gatsbyApi

      const sourcingTimer = reporter.activityTimer(`Sourcing from plugin API`)
      // highlight-next-line
      sourcingTimer.start()

      // Rest of sourceNodes
    }

    // Rest of file
    ```

    With the `start()` call the timer is running until you'll eventually call `end()`.

1. If the sourcing fails, the timer should be stopped. So update the `panicOnBuild` call to use the `sourcingTimer` instead:

    ```ts:title=plugin/src/source-nodes.ts
    // Imports

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      // Rest of sourceNodes

      if (errors) {
        // highlight-next-line
        sourcingTimer.panicOnBuild(errors[0].message)

        return
      }

      // Rest of sourceNodes
    }

    // Rest of file
    ```

    <Announcement>

    **Please note:** When looking at the TypeScript types for `sourcingTimer` you'll notice that it also implements `panicOnBuild` and `panic`. You can pass in the same arguments as you're used to, the `activityTimer` will handle everything for you. If you had continued to use `reporter.panicOnBuild` the terminal would output a successful message for the timer which is incorrect of course!

    </Announcement>

1. Right now the `sourcingTimer` shows "Sourcing from plugin API" together with a time in seconds. You can use the `setStatus()` method to append text.

    ```ts:title=plugin/src/source-nodes.ts
    // Imports

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      // Rest of sourceNodes

      const { posts = [], authors = [] } = data

      // highlight-start
      sourcingTimer.setStatus(
        `Processing ${posts.length} posts and ${authors.length} authors`
      )
      // highlight-end

      for (const post of posts) {
        nodeBuilder({ gatsbyApi, input: { type: NODE_TYPES.Post, data: post } })
      }

      for (const author of authors) {
        nodeBuilder({ gatsbyApi, input: { type: NODE_TYPES.Author, data: author } })
      }
    }

    // Rest of file
    ```

    <Announcement>

    **Pro tip:** Adding the information of how many types were created is a really nice debugging help since it'll allow folks to quickly compare builds and see if e.g. all the information they expect is sourced.

    </Announcement>

1. Stop the timer once node creation is done:

    ```ts:title=plugin/src/source-nodes.ts
    // Imports

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      // Rest of sourceNodes

      const { posts = [], authors = [] } = data

      sourcingTimer.setStatus(
        `Processing ${posts.length} posts and ${authors.length} authors`
      )

      for (const post of posts) {
        nodeBuilder({ gatsbyApi, input: { type: NODE_TYPES.Post, data: post } })
      }

      for (const author of authors) {
        nodeBuilder({ gatsbyApi, input: { type: NODE_TYPES.Author, data: author } })
      }

      // highlight-next-line
      sourcingTimer.end()
    }

    // Rest of file
    ```

1. Restart the `develop:site` script and you should read a new activity in the terminal:

    ```shell
    success Sourcing from plugin API - 0.019s - Processing 3 posts and 2 authors
    ```

    That's what we'd expect, nice!

### Task: Define custom errors

The error you've output to the terminal in [Task: Output errors](#task-output-errors) was functional but missing some crucial information. Where is the error coming from? Which part of the plugin? Any suggestions for immediate fixes?

You can use the `setErrorMap` method to create an error map for your plugin. Gatsby internally uses an error map to benefit from two things (among other additional benefits):

- A user can google the error with its unique ID and hopefully only find resources that are directly related to that error.
- One can track down the location in the source code where an error is thrown quicker through the use of constants and unique IDs.

Both benefits also apply to you when you define a custom error map for your plugin. You'll see the improvement at the end of this task. As a reminder, this is how an error (for the data fetching step) currently looks like:

```shell
ERROR  UNKNOWN

Cannot query field "title2" on type "Post". Did you mean "title"?
```

Let's get to work:

1. Open the `plugin/src/constants.ts` file and add an `ERROR_CODES` map with its first key `GraphQLSourcing`:

    ```ts:title=plugin/src/constants.ts
    export const NODE_TYPES = {
      Post: `Post`,
      Author: `Author`,
    } as const

    // highlight-start
    export const ERROR_CODES = {
      GraphQLSourcing: `10000`,
    } as const
    // highlight-end
    ```

    The keys of the `ERROR_CODES` object can be arbitrary, we'd recommend using names that make it easy to differentiate them for their different purposes. The value of each entry is the unique ID. So make sure that each ID only exists once inside `ERROR_CODES`.

    You don't need to worry about other plugins or Gatsby itself as the IDs are namespaced with your plugin. If possible, use IDs that are not already used by Gatsby itself.

1. Import `ERROR_CODES` into `plugin/src/on-plugin-init.ts` and initialize `setErrorMap`. Remove the existing `info` call:

    ```ts:title=plugin/src/on-plugin-init.ts
    import type { GatsbyNode } from "gatsby"
    // highlight-next-line
    import { ERROR_CODES } from "./constants"

    export const onPluginInit: GatsbyNode[`onPluginInit`] = ({ reporter }) => {
      // highlight-next-line
      reporter.setErrorMap({})
    }
    ```

1. You can populate the error map like so:

    - The object's keys are the unique IDs
    - The object should contain these three keys and values:
      - `text`: Your text that will be shown to the user for this specific error
      - `level`: Set this to `ERROR`
      - `category`: Set this to `THIRD_PARTY`

    Add the following to `plugin/src/on-plugin-init.ts`:

    ```ts:title=plugin/src/on-plugin-init.ts
    import type { GatsbyNode } from "gatsby"
    import { ERROR_CODES } from "./constants"

    export const onPluginInit: GatsbyNode[`onPluginInit`] = ({ reporter }) => {
      reporter.setErrorMap({
        [ERROR_CODES.GraphQLSourcing]: {
          text: (context) => `${context.sourceMessage}: ${context.graphqlError}`,
          level: `ERROR`,
          category: `THIRD_PARTY`,
        },
      })
    }
    ```

    `text` has to be a function that returns a `string` and receives a freeform `context`. You can pass through any information you'd like in `context` and then use it to construct the final message that is shown to the user. So the error above requires `sourceMessage` and `graphqlError` to work.

    <Announcement>

    **Pro tip:** If your `text` should be a multi-line string, consider using the `stripIndent` function from [common-tags](https://www.npmjs.com/package/common-tags#stripindent). This way your error will be displayed correctly independent from how you write the string.

    </Announcement>

1. In this step you'll invoke the error with the custom ID and pass along the required `context` information. Open `plugin/src/source-nodes.ts` and add the `ERROR_CODES` import and use it inside the existing `panicOnBuild` call:

    ```ts:title=plugin/src/source-nodes.ts
    import type { GatsbyNode, SourceNodesArgs, NodeInput } from "gatsby"
    import { fetchGraphQL } from "./utils"
    import type { IAuthorInput, IPostInput, NodeBuilderInput } from "./types"
    // highlight-next-line
    import { NODE_TYPES, ERROR_CODES } from "./constants"

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      // Rest of sourceNodes

      if (errors) {
        // highlight-start
        sourcingTimer.panicOnBuild({
          id: ERROR_CODES.GraphQLSourcing,
          context: {
            sourceMessage: `Sourcing from the GraphQL API failed`,
            graphqlError: errors[0].message,
          },
        })
        // highlight-end

        return
      }

      // Rest of sourceNodes
    }

    // Rest of file
    ```

1. As in step 3 of [Task: Output errors](#task-output-errors), add a typo to the GraphQL error again (e.g. `title2`), restart the `develop:site` script and you should see a new output for the error:

    ```shell
    ERROR #plugin_10000  PLUGIN

    Sourcing from the GraphQL API failed: Cannot query field "title2" on type "Post". Did you mean "title"?

    failed Sourcing from plugin API - 0.029s
    ```

Your error now has a custom ID (`#plugin_10000`), it prints the name of the plugin (`PLUGIN`), and you've added additional context to the error itself (`Sourcing from the GraphQL API failed:`). Whenever you need to output an error message, create a new structured error inside `setErrorMap` and use it to display the best possible error to the user.

## `cache` API

You might already know that Gatsby creates two folders during its build phase: `.cache` and `public`. It's recommended to keep both folders around in between builds as then Gatsby can leverage its caching functionalities the best.

With the [cache API](/docs/reference/config-files/node-api-helpers/#cache) you have access to a key-value store to persist data inside the `.cache` folder. You can use it to persist timestamps, tokens, etc. to use delta updates, or to store the artifacts of time/memory/cpu intensive tasks (e.g. images, big files).

If the term "delta update" doesn't ring a bell, here's a short explanation in context of a source plugin: A delta update requires the source plugin to only fetch data from the remote API that is new, or has been changed from a previous state, in contrast to having to fetch all the data again.

<Announcement>

**Example usage:** Your API endpoint accepts an optional query parameter of `lastFetched`. If used, only updates since that date are returned. When storing the timestamp of last sourcing with the `cache` API you then can use that date in your request, e.g. `http://yourapi.com/endpoint?lastFetched=2023-01-1`.

</Announcement>

**Important:** Your remote API has to support delta updates, it's not a universal thing that works everywhere.

### Task: Save the timestamp of last sourcing

The most common use case of the `cache` API is to store timestamps, tokens, etc. and therefore you'll learn exactly that. Even though the example API this tutorial uses doesn't support delta updates, you'll go through all the necessary steps in this task to apply it to your own source plugin.

The `cache` API is a key-value store, so you save specific data under a key, and then can retrieve a value with a given key.

1. Store the name of the key in an constants to minimize the likelyhood of typos. Open `plugin/src/constants.ts` and add:

    ```ts:title=plugin/src/constants.ts
    export const NODE_TYPES = {
      Post: `Post`,
      Author: `Author`,
    } as const

    export const ERROR_CODES = {
      GraphQLSourcing: `10000`,
    } as const

    // highlight-start
    export const CACHE_KEYS = {
      Timestamp: `timestamp`,
    } as const
    // highlight-end
    ```

1. Import the new `CACHE_KEYS` variable into `plugin/src/source-nodes.ts` and grab the `cache` API from `gatsbyApi` inside `sourceNodes`:

    ```ts:title=plugin/src/source-nodes.ts
    // Rest of imports
    // highlight-next-line
    import { NODE_TYPES, ERROR_CODES, CACHE_KEYS } from "./constants"

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      // highlight-next-line
      const { reporter, cache } = gatsbyApi

      // Rest of sourceNodes
    }

    // Rest of file
    ```

1. Generate a date timestamp just before the `fetchGraphQL` call and use `cache.set` to save the timestamp after the successful fetch. This way you're ensuring accurate timestamps that are only saved when a successful data call happened. **The key you use for `cache.set` has to be unique.**

    ```ts:title=plugin/src/source-nodes.ts
    // Imports

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      // Rest of sourceNodes

      // highlight-next-line
      const lastFetchedDateCurrent = Date.now()

      const { data, errors } = await fetchGraphQL<IApiResponse>(/* Query */)

      if (errors) {
        sourcingTimer.panicOnBuild({
          id: ERROR_CODES.GraphQLSourcing,
          context: {
            sourceMessage: `Sourcing from the GraphQL API failed`,
            graphqlError: errors[0].message,
          },
        })

        return
      }

      // highlight-next-line
      await cache.set(CACHE_KEYS.Timestamp, lastFetchedDateCurrent)

      // Rest of sourceNodes
    }

    // Rest of file
    ```

    <Announcement>

    `Date.now()` returns the number of milliseconds elapsed since the epoch. If you need another format, read the [Date.now() documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now).

    </Announcement>

    It's also important to know that `cache` is shared by all instances of your plugin. So if your user can add your plugin multiple times to `gatsby-config` (e.g. by setting a different `workspaceId` in the plugin options), you need to ensure that those `cache.set` calls use unique keys. In those cases, use a unique instance identifier, for example said plugin options.

1. The next step is to use `cache.get` to retrieve the timestamp from the cache before the `fetchGraphQL` call. Since you won't use it for delta fetching in this tutorial, output the information in the `verbose` mode of Gatsby with `reporter.verbose`:

    ```ts:title=plugin/src/source-nodes.ts
    // Imports

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (gatsbyApi) => {
      // Rest of sourceNodes

      // highlight-next-line
      const lastFetchedDate: number = await cache.get(CACHE_KEYS.Timestamp)
      const lastFetchedDateCurrent = Date.now()

      // highlight-next-line
      reporter.verbose(`[plugin] Last fetched date: ${lastFetchedDate}`)

      // Rest of sourceNodes
    }

    // Rest of file
    ```

1. Time to test your changes! Restart the `develop:site` script but this time with the `--verbose` flag enabled:

    ```shell
    yarn develop:site --verbose
    ```

    The output got a lot busier! You should see a new log:

    ```shell
    verbose [plugin] Last fetched date: undefined
    ```

    You're seeing this because on this first run the `lastFetchedDate` didn't exist yet and thus `undefined` is returned from the `cache.get` call. Stop the development server and restart the script. You now should see a timestamp defined that looks something like this:

    ```shell
    verbose [plugin] Last fetched date: 1677669643651
    ```

    Great, it's working!

    <Announcement>

    **Pro tip:** If you save files with the `cache` API you can also inspect what is saved by going to the `.cache/caches` folder and looking for a folder named like your plugin. For this tutorial the path would be `site/.cache/caches/plugin`.

    </Announcement>

## `pluginOptionsSchema` API

In most cases a Gatsby source plugin needs one or more options as it interacts with third-party APIs that require customization and authentication (e.g. different endpoints or API keys). You don't want other users to use your credentials, so you'll need a way for your users to provide theirs. That's where plugin options come in!

The [Configuring Plugin Usage with Plugin Options](/docs/how-to/plugins-and-themes/configuring-usage-with-plugin-options/) guide goes into depth about plugin options and the [`pluginOptionsSchema` API](/docs/reference/config-files/gatsby-node/#pluginOptionsSchema) so if you want to know more beyond this guide, be sure to give it a read. But to give a short summary:

- A Gatsby plugin with options included makes those options available in the second argument of Gatsby [Node](/docs/reference/config-files/gatsby-node/), [Browser](/docs/reference/config-files/gatsby-browser/), and [SSR](/docs/reference/config-files/gatsby-ssr/) APIs.

    A sample `gatsby-config` file:

    ```js:title=gatsby-config.js
    module.exports = {
      plugins: [
        {
          resolve: `gatsby-plugin-console-log`,
          options: { message: "Hello world" },
        },
      ],
    }
    ```

    Usage of that option in a Node API:

    ```js:title=plugins/gatsby-plugin-console-log/gatsby-node.js
    exports.onPreInit = (_, pluginOptions) => {
      console.log(
        `logging: "${pluginOptions.message}" to the console` // highlight-line
      )
    }
    ```
- A plugin can optionally define a schema (through the `pluginOptionsSchema` API) to enforce a type for each option. Gatsby will validate that the options users pass match the schema to help them correctly set up their site.

    ```js:title=plugins/gatsby-plugin-console-log/gatsby-node.js
    exports.pluginOptionsSchema = ({ Joi }) => {
      return Joi.object({
        message: Joi.string()
          .required()
          .description(`The message logged to the console.`),
      })
    }
    ```

Here are some tips when working with plugin options:

- **Consider the options a public API contract between your users and your plugin.** Follow [SemVer](https://semver.org/) versioning when adding, modifying, and removing options. For example, if you want to remove an option you should only do this in a major release of your plugin since it's a breaking change.
- **Add as few plugin options as necessary.** In the beginning, use safe defaults where possible (to the best of your knowledge) and when users wish to have new options available, seriously contemplate if you should add them. Once added, you'll need to maintain the option forever (or until you remove it again).
- **Use clear names.** Naming things is hard, but take extra care in using clear names for your plugin's options. Best case scenario is that an option is clear enough without any additional description.
- **Use "enable" options, not negated options.** An example: Instead of using `noWarnings: true`, use `warnings: false`. The double negation that would occur with `noWarnings: false` is really confusing.
- **Pass options through.** If your source plugin is using a library under the hood (e.g. an SDK from a CMS) that has options on its own, allow people to configure all those options, too. You could collect those under a new key in the `options` object (e.g. `options.sdkOptions`) and then refer people to the SDK's documentation.

### Task: Use `endpoint` option

Instead of hardcoding the URL to the GraphQL API, you'll use an `endpoint` option and thus make the URL configurable.

1. The boilerplate you cloned at the beginning already has an `endpoint` option defined in the site's `gatsby-config`. Go to `site/gatsby-config.ts` to inspect the code:

    ```ts:title=site/gatsby-config.ts
    import type { GatsbyConfig } from "gatsby"
    import type { IPluginOptions } from "plugin"

    // highlight-next-line
    const GRAPHQL_ENDPOINT = `http://localhost:4000/graphql`

    const config: GatsbyConfig = {
      graphqlTypegen: true,
      plugins: [
        {
          resolve: `plugin`,
          options: {
            // highlight-next-line
            endpoint: GRAPHQL_ENDPOINT,
          } satisfies IPluginOptions,
        },
        `gatsby-plugin-image`,
        `gatsby-plugin-sharp`,
        `gatsby-transformer-sharp`,
      ],
    }

    export default config
    ```

    So the plugin already receives the GraphQL endpoint through the `endpoint` option.

1. The `IPluginOptionsKeys` TypeScript type inside `plugin/src/types.ts` currently has a generic `[key: string]: any` catch-all. Replace it with your `endpoint` option so that both internally and externally the types are correct:

    ```ts:title=plugin/src/types.ts
    // Rest of types

    interface IPluginOptionsKeys {
      // highlight-next-line
      endpoint: string
    }

    // Rest of types
    ```

1. Open the file `plugin/src/source-nodes.ts` and validate that you can access the option. You can do this by accessing the second parameter of the `sourceNodes` function and `console.log` it. Use the `IPluginOptionsInternal` type for it:

    ```ts:title=plugin/src/source-nodes.ts
    // Rest of imports
    import type {
      IAuthorInput,
      IPostInput,
      NodeBuilderInput,
      // highlight-next-line
      IPluginOptionsInternal,
    } from "./types"

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (
      gatsbyApi,
      // highlight-next-line
      pluginOptions: IPluginOptionsInternal
    ) => {
      const { reporter, cache } = gatsbyApi

      // highlight-next-line
      console.log({ pluginOptions })

      // Rest of sourceNodes
    }

    // Rest of file
    ```

1. Restart the `develop:site` script and you should see an output in the terminal like this:

    ```shell
    {
      pluginOptions: { plugins: [], endpoint: 'http://localhost:4000/graphql' }
    }
    ```

    Don't worry about the `plugins` key there. Since Gatsby's plugins can also have sub-plugins, the key `plugins` is added by default to the `pluginOptions` object. What you really should care about is that the `endpoint` option successfully comes through.

1. Destructure `endpoint` from the `pluginOptions` object and use it in the `fetchGraphQL` call:

    ```ts:title=plugin/src/source-nodes.ts
    // Imports

    export const sourceNodes: GatsbyNode[`sourceNodes`] = async (
      gatsbyApi,
      pluginOptions: IPluginOptionsInternal
    ) => {
      const { reporter, cache } = gatsbyApi
      // highlight-next-line
      const { endpoint } = pluginOptions

      // Rest of sourceNodes

      const { data, errors } = await fetchGraphQL<IApiResponse>(
        // highlight-next-line
        endpoint,
        `/* GraphQL Query */`
      )

      // Rest of sourceNodes
    }

    // Rest of file
    ```

    Once again, restart the `develop:site` script and you should see the log `Sourcing from plugin API - 0.033s - Processing 3 posts and 2 authors` to indicate that the sourcing was successful.

You've successfully used a plugin option in your source plugin's source code.

### Task: Verify `endpoint` option

Right now a user could use any serializable value for `endpoint` like numbers, objects, strings, or booleans. But those are not valid options for `endpoint`, it should be a string, more specifically a valid URI. Use the `pluginOptionsSchema` API to validate exactly that.

1. Create a new file called `plugin-options-schema.ts` inside the plugin's `src` folder with the following contents:

    ```ts:title=plugin/src/plugin-options-schema.ts
    import type { GatsbyNode } from "gatsby"
    import type { ObjectSchema } from "gatsby-plugin-utils"

    export const pluginOptionsSchema: GatsbyNode["pluginOptionsSchema"] = ({
      Joi,
    }): ObjectSchema => {
      return Joi.object({})
    }
    ```

    `pluginOptionsSchema` uses [Joi](https://joi.dev/) to define a validation schema. Use its [API documentation](https://joi.dev/api/) to discover all available options.

1. Define `endpoint` in the schema. It should be a string (a valid URI), should be required, and have a short description. You can write something like this:

    ```ts:title=plugin/src/plugin-options-schema.ts
    import type { GatsbyNode } from "gatsby"
    import type { ObjectSchema } from "gatsby-plugin-utils"

    export const pluginOptionsSchema: GatsbyNode["pluginOptionsSchema"] = ({
      Joi,
    }): ObjectSchema => {
      return Joi.object({
        // highlight-start
        endpoint: Joi.string()
          .uri()
          .required()
          .description(`The endpoint of your GraphQL API`),
        // highlight-end
      })
    }
    ```

1. Export the `pluginOptionsSchema` API in the plugin's `gatsby-node` file so that it is run:

    ```ts:title=plugin/src/gatsby-node.ts
    export type { IPluginOptions } from "./types"

    export { onPluginInit } from "./on-plugin-init"
    export { sourceNodes } from "./source-nodes"
    export { createSchemaCustomization } from "./create-schema-customization"
    // highlight-next-line
    export { pluginOptionsSchema } from "./plugin-options-schema"
    ```

1. You can test that it works correctly by opening up the site's `gatsby-config` and changing the option to an invalid value, e.g. entering a number:

    ```ts:title=site/gatsby-config.ts
    {
      resolve: `plugin`,
      options: {
        // highlight-next-line
        endpoint: 12345,
      } satisfies IPluginOptions,
    },
    ```

    Restart the `develop:site` script. The development server should crash and show an error:

    ```shell
    ERROR #11331  API.NODE.VALIDATION

    Invalid plugin options for "plugin":

    - "endpoint" must be a string
    ```

    It's working correctly, great! Go back to `site/gatsby-config.ts` and use the correct `endpoint` option again.

Joi as a validation library is really powerful and as shown in [Configuring Plugin Usage with Plugin Options](/docs/how-to/plugins-and-themes/configuring-usage-with-plugin-options/) you can define a really fine-grained validation schema.

## Summary

Awesome! You've learned a lot about plugin options.

Take a moment to think back on what you've learned so far. Challenge yourself to answer the following questions from memory:

- How can you use the `reporter` API and what methods does it have?
- What is the difference between the methods `panicOnBuild` and `panic`?
- How can you show an activity for e.g. the time it takes to source data?
- What are the advantages of custom errors defined through `setErrorMap`?
- Which type of storage is the `cache` API and for which purposes is it useful?
- How can you access plugin options?
- How can you validate that the user input for plugin options is correct?

### Key takeaways

- Use the `reporter` API and its methods to output information (warnings, errors, logs, timers, etc.) to the terminal during `gatsby develop` and `gatsby build`. You can also stop the entire Gatsby process on errors if necessary.
- By default, a generic error handler is used. You can use the `setErrorMap` API to define your own, custom errors to show richer information to your users.
- The `cache` API gives you access to a key-value store to save data between runs.
- Use the `pluginOptionsSchema` API to validate the input of your plugin options.

<Announcement>

**Share Your Feedback!**

Our goal is for this tutorial to be helpful and easy to follow. We'd love to hear your feedback about what you liked or didn't like about this part of the tutorial.

Use the "Was this doc helpful to you?" form at the bottom of this page to let us know what worked well and what we can improve.

</Announcement>

### What's coming next?

In Part 5 you'll learn all about Incremental Builds and how to best build your API and plugin for it.

<LinkButton
  to="/docs/tutorial/creating-a-source-plugin/part-5/"
  rightIcon={<MdArrowForward />}
  variant="SECONDARY"
>
  Continue to Part 5
</LinkButton>